﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using CrawlerCore;
using HtmlAgilityPack;

namespace WCrawlerLib
{
    public abstract class BrowserSession_Forum : BrowserSession
    {
        private enumForumType _forumTypeId;
        public enumForumType ForumTypeId
        {
            get
            {
                return _forumTypeId;
            }
            set
            {
                _forumTypeId = value;
            }
        }

        #region Abstract Functions

        public abstract int GetUserId(string forumUrl, string userName);

        public abstract bool DoLogin(string forumUrl, string loginUrl, string postParams, string username, string password);

        public abstract void PostNewThread(string forumUrl, string newThreadurl, string postParams, string subject,
                                           string message, string fId, string icon, string tagList, string prefixId);

        public abstract bool PostReply(string topicUrl, string forumUrl, string message);

        public abstract bool PostNewReply(string forumUrl, string topicUrl, string newReplyUrl, string postParams,
                                          string message);

        public abstract bool EditPost(string forumUrl, string topicUrl, string postId, string editParams, string subject,
                                      string message, string updateReason, string icon);

        public abstract List<DataRow> SearchThreadsByPaging(string forumUrl, HtmlDocument doc, DataTable table);

        public abstract string SearchThreadsInTopic(string postTitle, string topicUrl, string searchTopic_xPath,
                                                    string username);

        public virtual DataTable SearchForumThreads(string forumUrl, string statisticUrl, string xPath_Paging, DataTable table, bool isPaging, bool isGetFirstThreads)
        {
            List<string> urlList = new List<string>();

            //===============================================================================
            //THE FIRST PAGE
            //Get full statistic url in then first page
            statisticUrl = GeneralClass.GetFullUrl(forumUrl, statisticUrl);
            Get(statisticUrl);
            //Get all urls of threads in the first page
            List<DataRow> firstRowList = SearchThreadsByPaging(forumUrl, HtmlDoc, table);
            foreach (DataRow row in firstRowList)
            {
                string url = GeneralClass.GetFullUrl(forumUrl, row["Url"].ToString());
                //Get threadnum, postnum in url
                string[] result = GetThreadPostNum(url);
                row["ThreadNum"] = result[0];
                row["PostNum"] = result[1];
                //Save in datatable
                table.Rows.Add(row);

                //if only get first thread then return
                if (isGetFirstThreads)
                    return table;
            }

            //===============================================================================
            //NEXT STATISTIC PAGES IN PAGING
            //If find threads in other paging
            if (isPaging && !string.IsNullOrEmpty(xPath_Paging))
            {
                //Find urls in pages if paging
                string xPath = xPath_Paging; //@".//a[contains(@href, '&page=')]";
                HtmlNodeCollection nodeCollection = HtmlDoc.DocumentNode.SelectNodes(xPath);
                if (nodeCollection != null)
                {
                    object lockRow = new object();
                    object lockList = new object();
                    Parallel.ForEach<HtmlNode>(nodeCollection, node =>
                                                                   {
                                                                       string statisticUrlPaging = node.Attributes["href"].Value;
                                                                       if (!urlList.Contains(statisticUrlPaging))
                                                                       {
                                                                           lock (lockList)
                                                                           {
                                                                               urlList.Add(statisticUrlPaging);
                                                                           }

                                                                           statisticUrlPaging = GeneralClass.GetFullUrl(forumUrl, statisticUrlPaging);
                                                                           HtmlDocument doc = Get(statisticUrlPaging);
                                                                           List<DataRow> rowList = SearchThreadsByPaging(forumUrl, doc, table);
                                                                           foreach (DataRow row in rowList)
                                                                           {
                                                                               lock (lockRow)
                                                                               {
                                                                                   string url = GeneralClass.GetFullUrl(forumUrl, row["Url"].ToString());
                                                                                   //Get threadnum, postnum in url
                                                                                   string[] result = GetThreadPostNum(url);
                                                                                   row["ThreadNum"] = result[0];
                                                                                   row["PostNum"] = result[1];
                                                                                   //Save in datatable
                                                                                   table.Rows.Add(row);
                                                                               }
                                                                           }
                                                                       }
                                                                   });
                }
            }
            return table;
        }

        /// <summary>
        /// Get thread num and post num in thread url
        /// </summary>
        /// <param name="threadUrl"></param>
        /// <returns></returns>
        public string[] GetThreadPostNum(string threadUrl)
        {
            HtmlDocument doc = Get(threadUrl);
            string[] result = new string[2];
            if (doc != null)
                result = GetThreadPostNum(doc);
            return result;
        }

        /// <summary>
        /// Get thread num and post num in thread url
        /// </summary>
        /// <param name="threadUrl"></param>
        /// <returns></returns>
        public abstract string[] GetThreadPostNum(HtmlDocument doc);

        public abstract string FValue_Pattern();
        #endregion

        

        /// <summary>
        /// Get group title include titles of subforums 
        /// .//ol/li[.//a[contains(@href,'forumdisplay.php')]] 
        /// .//ol/li//a[not(contains(@href,'.html'))]
        /// </summary>
        /// <param name="forum"></param>
        /// <param name="xPath"></param>
        /// <returns></returns>
        public Dictionary<string, string> SearchTopics(HtmlDocument doc, string xPath, string blackListUrl)
        {
            Dictionary<string, string> links = new Dictionary<string, string>();
            try
            {
                HtmlNodeCollection nodeCollection = doc.DocumentNode.SelectNodes(xPath);
                if (nodeCollection != null)
                {
                    Regex r = new Regex(blackListUrl);
                    foreach (HtmlNode href in nodeCollection)
                    {
                        //get url
                        string url = System.Web.HttpUtility.UrlDecode(href.Attributes["href"].Value);
                        //get title of url
                        HtmlNodeCollection titleCollection = href.SelectNodes(".//text()");
                        string title = "";
                        if (titleCollection != null)
                            title = System.Web.HttpUtility.HtmlDecode(href.SelectNodes(".//text()")[0].InnerHtml);
                        //Add url
                        if (!string.IsNullOrEmpty(blackListUrl))
                        {
                            //check url contains blaclist?
                            Match m = r.Match(url.ToLower());
                            if (!m.Success)
                            {
                                if (!links.ContainsKey(url))
                                    links.Add(url, title);
                            }
                        }
                        else
                        {
                            if (!links.ContainsKey(url))
                                links.Add(url, title);
                        }
                    }
                }
            }
            catch (WebException we)
            {
                throw we;
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return links;
        }

        /// <summary>
        /// Return result in two key "type" and "forumtype" of Dictionary
        /// </summary>
        /// <param name="doc"></param>
        /// <returns></returns>
        public Dictionary<string, string> GetForumType(HtmlDocument doc)
        {
            Dictionary<string, string> list = new Dictionary<string, string>();
            list.Add("type", "*Unknown*");
            list.Add("version", "");
            //string htmlContent = System.Web.HttpUtility.HtmlEncode(doc.DocumentNode.InnerHtml.ToLower());
            string htmlContent = doc.DocumentNode.InnerHtml.ToLower();
            //*****************************************************************
            //Search phpBB3
            //*****************************************************************
            string pattern = @"ucp\.php\?mode\=(login|register|logout)";
            Regex r = new Regex(pattern);
            Match m = r.Match(htmlContent);
            if (m.Success)
            {
                list["type"] = "phpbb3";
                list["version"] = "3";
            }

            //*****************************************************************
            //Search phpBB2
            //*****************************************************************
            if (list["type"] == "*Unknown*")
            {
                pattern = @"profile\.php\?mode\=(register|editprofile)";
                r = new Regex(pattern);
                m = r.Match(htmlContent);
                if (m.Success)
                {
                    list["type"] = "phpbb2";
                    list["version"] = "2";
                }
            }

            //*****************************************************************
            //Search ipBoard
            //*****************************************************************
            if (list["type"] == "*Unknown*")
            {
                //pattern = @"app\=core\&amp\;(amp\;|)module\=global\&amp\;(amp\;|)section\=(login|register|login\&amp\;(amp\;|)do\=logout)";
                pattern = @"app\=core\&module\=global\&section\=(login|register)";
                r = new Regex(pattern);
                m = r.Match(htmlContent);
                if (m.Success)
                {
                    //Get Version
                    pattern = @"ipbv\=(?<version>([0-9]+))";
                    //pattern = @"(?<type>ip(\.|)board)(</a>|)\s*(?<version>([0-9]\.[0-9]\.[0-9]|))";
                    r = new Regex(pattern);
                    m = r.Match(htmlContent);
                    if (m.Success)
                    {
                        if (!string.IsNullOrEmpty(m.Result("${version}").ToString()))
                        {
                            list["version"] = m.Result("${version}").ToString();
                            if (list["version"].Contains("31"))
                                list["type"] = "IPB31x";
                            else if (list["version"].Contains("32"))
                                list["type"] = "IPB32x";
                        }
                    }
                    else
                    {
                        list["type"] = "IPB";
                    }
                }
            }

            //******************************************************************************
            //Search vBulletin 
            //******************************************************************************
            if (list["type"] == "*Unknown*")
            {
                //First, find patterns as "vbulletin x.x.x"
                //pattern = @"vbulletin(\&\#8482\;|\&reg\;|\&trade\;|)(</a>|)\s*(version|)\s*((?<version>([0-9]\.[0-9]\.[0-9]))|)";
                pattern = @"vbulletin";
                if (htmlContent.Contains(pattern))
                {
                    if (Response.Cookies["bblastactivity"] != null)
                        list["type"] = "vBulletin3x";
                    else if (Response.Cookies["bb_lastactivity"] != null)
                        list["type"] = "vBulletin4x";
                    else
                        list["type"] = "vBulletin";

                    pattern = @"vbulletin.*(version|)\s*(?<version>([0-9]+\.[0-9]+\.[0-9]+))";
                    r = new Regex(pattern);
                    m = r.Match(htmlContent);
                    if (m.Success)
                    {
                        //Search forum type again
                        if (!string.IsNullOrEmpty(m.Result("${version}").ToString()))
                        {
                            list["version"] = m.Result("${version}").ToString();
                            if (list["type"] == "*Unknown*" || list["type"] == "vBulletin")
                            {
                                if (list["version"][0].ToString() == "3")
                                    list["type"] = "vBulletin3x";
                                else if (list["version"][0].ToString() == "4")
                                    list["type"] = "vBulletin4x";
                            }
                        }
                    }
                }
            }
            return list;
        }

        //public Dictionary<string, string> GetAllLinks(string forumUrl, HtmlAgilityPack.HtmlDocument doc, string blackListUrl)
        //{
        //    Dictionary<string, string> links = new Dictionary<string, string>();
        //    string url = "", title = "";
        //    HtmlNodeCollection nc = doc.DocumentNode.SelectNodes(".//a");
        //    if (nc == null)
        //        return links;

        //    Regex r = new Regex(blackListUrl);
        //    //get all links in forum if xpath not found
        //    foreach (HtmlNode link in nc)
        //    {
        //        foreach (HtmlNode node in link.ChildNodes)
        //        {
        //            if (node.Name == "strong" || node.Name == "#text" || node.Name == "span")
        //            {
        //                url = System.Web.HttpUtility.UrlDecode(link.Attributes["href"].Value);
        //                title = System.Web.HttpUtility.HtmlDecode(node.InnerHtml);
        //                //Kiểm tra xem nội dung bên trong node có empty không
        //                if (!string.IsNullOrEmpty(url) &&
        //                    !string.IsNullOrEmpty(title))
        //                {
        //                    if (!string.IsNullOrEmpty(blackListUrl))
        //                    {
        //                        //check url contains blaclist?
        //                        Match m = r.Match(url.ToLower());
        //                        if (!m.Success)
        //                        {
        //                            //If contains url not is forumUrl then ignore
        //                            if (url.Contains("http://") && 
        //                                !url.Contains(GeneralClass.GetName(forumUrl)))
        //                                continue;

        //                            if (!links.ContainsKey(url))
        //                                links.Add(url, title);
        //                        }
        //                    }
        //                    else
        //                    {
        //                        if (!links.ContainsKey(url))
        //                            links.Add(url, title);
        //                    }

        //                }
        //            }
        //        }
        //    }
        //    return links;
        //}

        /// <summary>
        /// Search f value in button New Thread with url template do=newthread&f=[number]
        /// </summary>
        /// <param name="threadUrl"></param>
        /// <returns></returns>
        public string FindNewThreadValue(string threadUrl, string pattern)
        {
            string fValue = "";
            HtmlDocument doc;
            try
            {
                doc = Get(threadUrl);
                if (doc.DocumentNode != null)
                {
                    //string pattern = @"do\=newthread\&f=(?<fValue>([0-9]+))";
                    Regex r = new Regex(pattern);
                    Match m = r.Match(doc.DocumentNode.InnerHtml);
                    if (m.Success)
                        fValue = m.Result("${fValue}");
                }
            }
            catch (WebException we)
            {
                //if (we.Message.Contains("The operation has timed out")
                if (we.Message.Contains("Unable to connect to the remote server")
                    || we.Message.Contains("Object reference not set to an instance of an object"))
                    fValue = FindNewThreadValue(threadUrl, pattern);
                else
                {
                    //if (!we.Message.Contains("The operation has timed out"))
                    //    throw new InvalidOperationException(we.Message);
                }

            }
            catch (Exception ex)
            {
                //if (ex.Message.Contains("The operation has timed out")
                if (ex.Message.Contains("Unable to connect to the remote server")
                    || ex.Message.Contains("Object reference not set to an instance of an object"))
                    fValue = FindNewThreadValue(threadUrl, pattern);
                else
                {
                    //if (!ex.Message.Contains("The operation has timed out"))
                    //    throw new InvalidOperationException(ex.Message);
                }
            }
            return fValue;
        }

        

        /// <summary>
        /// Function encrypt password
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public string GetMD5Hash(string input)
        {
            System.Security.Cryptography.MD5CryptoServiceProvider x = new System.Security.Cryptography.MD5CryptoServiceProvider();
            byte[] bs = System.Text.Encoding.UTF8.GetBytes(input);
            bs = x.ComputeHash(bs);
            System.Text.StringBuilder s = new System.Text.StringBuilder();
            foreach (byte b in bs)
            {
                s.Append(b.ToString("x2").ToLower());
            }
            string password = s.ToString();
            return password;
        }

        /// <summary>
        /// Download image from image url
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="url"></param>
        

        //public Dictionary<string, string> CheckLinks(Dictionary<int, string> fileDict)
        //{
        //    Dictionary<string, string> result = new Dictionary<string, string>();
        //    object objLock = new object();
        //    int i = 0;
        //    Parallel.ForEach(fileDict, p=>
        //    {
        //        Thread.Sleep(i * 2000);
        //        lock (objLock)
        //            i++;
        //        BrowserSession br = new BrowserSession_Forum();
        //        //br.PostParams = "links=" + p.Value +
        //        //                "&submit=" + "Check Links";
        //        //HtmlDocument doc =br.Post("http://urlchecker.net/");
        //        br.Get("http://wgtools.com");
        //        br.PostParams = "links=" + HttpUtility.UrlEncode(p.Value);
        //        HtmlDocument doc = br.Post("http://wgtools.com/link-checker/");
        //        HtmlNodeCollection nodeCollection = doc.DocumentNode.SelectNodes(".//table[@class='linkstable']//tr[td]");
        //        foreach (HtmlNode node in nodeCollection)
        //        {
        //            string status = node.SelectSingleNode(@"./td[2]/text()").InnerHtml.Trim();
        //            string file = HttpUtility.UrlDecode(node.SelectSingleNode(@"./td[3]/a/text()").InnerHtml.Trim());
        //            lock (objLock)
        //            {
        //                result.Add(file, status);
        //            }
        //        }
        //    });
        //    return result;
        //}

        

        ///// <summary>
        ///// Get hidden value by name
        ///// </summary>
        ///// <param name="name"></param>
        ///// <returns></returns>
        //public string GetValueInInputTag(HtmlDocument doc, string type, string keyName, string keyValue)
        //{
        //    string value = "";
        //    try
        //    {
        //        string pattern = string.Format(".//input[@type='{0}' and @{1}='{2}']", type, keyName, keyValue);
        //        HtmlNode node = doc.DocumentNode.SelectSingleNode(pattern);
        //        if (node != null)
        //            value = node.Attributes["value"].Value;
        //    }
        //    catch
        //    {
        //    }
        //    return value;
        //}


        ///// <summary>
        ///// Get value by regex pattern
        ///// </summary>
        ///// <param name="pattern"></param>
        //public string GetValueByRegexPattern(string pattern)
        //{
        //    string value = "";
        //    try
        //    {
        //        Regex r = new Regex(pattern);
        //        Match m = r.Match(HtmlDoc.DocumentNode.InnerHtml);
        //        if (m.Success)
        //            value = m.Result("${value}");
        //    }
        //    catch
        //    {
        //    }
        //    return value;
        //}
    }
}